概述
本节解决 PrismaModule 注册多个数据库连接时的区分问题。通过引入 connectionName(后简化为 name)作为 Provider 的注入标识,配合 Symbol 创建唯一 token,实现同一 PrismaModule 同时管理多个数据库连接。
问题场景
当 AppModule 注册多个 PrismaModule.forRoot() 时,无法区分不同的数据库连接:
// 无法区分两个连接
PrismaModule.forRoot({ url: 'mysql://...' }),
PrismaModule.forRoot({ url: 'postgresql://...' }),
typescript
Controller 中注入时不知道应该使用哪个连接。
解决方案:connectionName
创建常量 Token
使用 Symbol 创建全局唯一的注入标识:
// prisma/prisma.constants.ts
export const PRISMA_CONNECTION_NAME = Symbol('PRISMA_CONNECTION_NAME');
typescript
修改 Options 接口
在 PrismaModuleOptions 中添加 name 字段:
// prisma/prisma-options.interface.ts
export interface PrismaModuleOptions {
url?: string;
options?: Prisma.PrismaClientOptions;
name?: string; // 连接别名
}
typescript
Core Module 中使用 name
// prisma/prisma-core.module.ts
import { PRISMA_CONNECTION_NAME } from './prisma.constants';
export class PrismaCoreModule {
static forRoot(options: PrismaModuleOptions): DynamicModule {
const { url, options: prismaOptions = {}, name } = options;
// 构建 Client 配置
const newOptions: any = {
datasources: { db: { url } },
};
if (Object.keys(prismaOptions).length > 0) {
Object.assign(newOptions, prismaOptions);
}
// 使用 name 或默认 token 作为 Provider 标识
const providerName = name || PRISMA_CONNECTION_NAME;
const prismaClientProvider: Provider = {
provide: providerName,
useFactory: () => {
const dbType = getDBType(url);
if (dbType === 'mysql') {
return new MySQLClient(newOptions);
}
return new PGClient(newOptions);
},
};
return {
module: PrismaCoreModule,
providers: [prismaClientProvider],
exports: [prismaClientProvider],
};
}
}
typescript
简化参数传递
方案演进
初始方案需要传递完整的 options 对象:
PrismaModule.forRoot({
url: 'mysql://...',
name: 'prisma1',
})
typescript
优化为直接传递两个参数:
PrismaModule.forRoot('mysql://...', 'prisma1')
typescript
通过 rest 参数实现
// prisma/prisma.module.ts
export class PrismaModule {
static forRoot(
options: PrismaModuleOptions,
): DynamicModule;
static forRoot(
url: string,
name?: string,
): DynamicModule;
static forRoot(
arg: PrismaModuleOptions | string,
...args: any[]
): DynamicModule {
let options: PrismaModuleOptions;
if (typeof arg === 'string') {
options = {
url: arg,
name: args.length > 0 ? args[0] : undefined,
};
} else {
options = arg;
}
return {
module: PrismaModule,
imports: [PrismaCoreModule.forRoot(options)],
};
}
}
typescript
在 AppModule 中使用
// app.module.ts
@Module({
imports: [
PrismaModule.forRoot(
'postgresql://pg_user:example@localhost:5432/test_db',
'prisma1',
),
PrismaModule.forRoot(
'mysql://root:password@localhost:3306/test_db',
'prisma2',
),
],
})
export class AppModule {}
typescript
在 Controller 中注入
通过 @Inject 和连接名称注入对应的 PrismaClient 实例:
// app.controller.ts
@Controller()
export class AppController {
constructor(
@Inject('prisma1') private readonly prisma1: PrismaClient,
@Inject('prisma2') private readonly prisma2: PrismaClient,
) {}
@Get('v1')
async helloV1() {
return this.prisma1.user.findMany();
}
@Get('v2')
async helloV2() {
return this.prisma2.user.findMany();
}
}
typescript
测试结果:
GET /v1返回 PostgreSQL 数据GET /v2返回 MySQL 数据
通过不同的注入名称成功区分了不同的数据库连接。
参考官方模块的扩展属性
参考 @nestjs/mongoose 的 MongooseModuleOptions,还可以扩展以下属性:
| 属性 | 类型 | 说明 |
|---|---|---|
retryAttempts | number | 最大重试次数(-1 为无限重试) |
retryDelay | number | 重试间隔(毫秒) |
connectionFactory | Function | 自定义连接创建过程 |
connectionErrorFactory | Function | 自定义错误处理 |
connectionFactory 的作用
类似 Vue 的生命周期钩子,允许用户在连接创建过程中插入自定义逻辑:
// 用户自定义连接创建
PrismaModule.forRoot({
url: 'mysql://...',
connectionFactory: (client) => {
// 可以在这里修改 client 配置
return client;
},
});
typescript
connectionErrorFactory 的作用
允许用户自定义连接错误的处理方式:
PrismaModule.forRoot({
url: 'mysql://...',
connectionErrorFactory: (error) => {
// 自定义错误处理逻辑
console.error('Database connection failed:', error);
return error;
},
});
typescript
关键要点
- Symbol 作为 Token -- 使用
Symbol创建全局唯一的注入标识,避免字符串冲突 - 参数简化 -- 从对象传参优化为位置参数,降低使用复杂度
- TypeScript 重载 -- 通过方法重载支持多种参数形式
- 工厂函数 --
connectionFactory和connectionErrorFactory提供类似生命周期钩子的扩展能力 - 参考官方模块 --
@nestjs/mongoose的mongoose-options.interface.ts是扩展属性的最佳参考
↑